package shell

import (
	"bytes"
	"fmt"
	"github.com/gofiber/fiber/v3"
	"log/slog"
	"os"
	"os/exec"
	"path/filepath"
)

var auditLogger *slog.Logger
var scriptBasePath string

func init() {
	f, err := os.Create("/tmp/audit.log")
	if err != nil {
		f = os.Stdout
	}
	handler := slog.NewJSONHandler(f, nil)
	auditLogger = slog.New(handler)

	// 执行脚本临时保存路径
	scriptBasePath = filepath.Join("/tmp", "script")
	_ = os.MkdirAll(scriptBasePath, 0666)
}

// Exec 支持执行单行Shell命令，支持环境变量
func Exec(ctx fiber.Ctx) error {
	body := new(Command)
	if err := ctx.Bind().Body(body); err != nil {
		return fiber.ErrBadRequest
	}

	var stdout = bytes.NewBuffer(make([]byte, 0, 512))
	cmd := exec.Command("sh", "-c", body.Content)
	cmd.Stdout = stdout
	cmd.Stderr = stdout

	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, body.Env...)

	err := cmd.Run()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	auditLogger.Info(body.Content, "user", ctx.UserContext().Value("user").(string), "type", "cmd")
	return ctx.SendStream(stdout)
}

// Script 支持执行脚本文件，需要提供执行程序路径和脚本文件
func Script(ctx fiber.Ctx) error {
	form, err := ctx.MultipartForm()
	if err != nil {
		return fiber.ErrBadRequest
	}

	executePath := ""
	executePaths := form.Value["executePath"]
	if len(executePaths) > 0 {
		executePath = executePaths[0]
	} else {
		return fiber.ErrBadRequest
	}

	// form-data 每个key可以多次传，因此每个值都是 []string
	env := form.Value["env"]

	files := form.File["script"]
	if len(files) <= 0 {
		return fiber.ErrBadRequest
	}

	file := files[0]

	scriptPath := filepath.Join(scriptBasePath, file.Filename)
	err = ctx.SaveFile(file, scriptPath)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, err.Error())
	}

	var stdout = bytes.NewBuffer(make([]byte, 0, 512))
	cmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", executePath, scriptPath))
	cmd.Stdout = stdout
	cmd.Stderr = stdout

	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, env...)

	err = cmd.Run()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	auditLogger.Info(scriptPath, "user", ctx.UserContext().Value("user").(string), "type", "script")
	return ctx.SendStream(stdout)
}
